﻿using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Configuration;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace BESClient
{
    public partial class Default : System.Web.UI.Page
    {
        string _inputFileExtention;
        protected void Page_Load(object sender, EventArgs e)
        {
        }
        protected void btnSubmit_Click(object sender, EventArgs e)
        {
            InvokeBatchExecutionService().Wait();
        }
        private async Task WriteFailedResponse(HttpResponseMessage response)
        {
            divResult.InnerHtml += string.Format("The request failed with status code: {0}", response.StatusCode);

            // Print the headers - they include the requert ID and the timestamp, which are useful for debugging the failure
            divResult.InnerHtml += response.Headers.ToString();

            string responseContent = await response.Content.ReadAsStringAsync();
            divResult.InnerHtml += responseContent;
        }
        private void SaveBlobToFile(AzureBlobDataReference blobLocation, string resultsLabel)
        {
            var credentials = new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials(blobLocation.SasBlobToken);
            var blobUrl = new Uri(new Uri(blobLocation.BaseLocation), blobLocation.RelativeLocation);
            var cloudBlob = new CloudBlockBlob(blobUrl, credentials);

            divResult.InnerHtml += string.Format("Result saved to blob: {0}<br/>", blobUrl.ToString());

            //download output file from browser (writes to downloads folder by default)
            string textOutput;
            string outputFileExtention = _inputFileExtention;//they should be the same
            using (var memoryStream = new MemoryStream())
            {
                //Read output blob
                cloudBlob.DownloadToStream(memoryStream);
                textOutput = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());

                string fileName = String.Format("output." + outputFileExtention, DateTime.Now.ToString("yyyy-MMM-dd-HHmmss"));
                Response.ContentType = "text/" + outputFileExtention;
                Response.AddHeader("content-disposition", "filename=" + fileName);

                // write string data to Response.OutputStream 
                Response.Write(textOutput);
            }
        }

        private void ProcessResults(BatchScoreStatus status)
        {

            var blobLocation = status.Result;
            SaveBlobToFile(blobLocation, "The output");
        }
        private async Task InvokeBatchExecutionService()
        {
            Util util = new Util();
            string BaseUrl = util.setPostURLString(txtUrl.Text);//remove ?api if passed in.
            string apiKey = txtKey.Text;
            string StorageAccountName = txtStAcctName.Text; 
            string StorageAccountKey = txtStorageKey.Text; 
            string StorageContainerName = txtContainer.Text;
            string InputBlobName = txtBlob.Text;

            divResult.InnerHtml += "Uploading the input to blob storage...<br/>";

            string storageConnectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", StorageAccountName, StorageAccountKey);
            var blobClient = CloudStorageAccount.Parse(storageConnectionString).CreateCloudBlobClient();
            var container = blobClient.GetContainerReference(StorageContainerName);
            container.CreateIfNotExists();
            var blob = container.GetBlockBlobReference(InputBlobName);

            //check to make sure a file is selected then upload to blob
            if (FileUpload1.HasFile)
            {
                blob.Properties.ContentType = FileUpload1.PostedFile.ContentType;
                blob.UploadFromStream(FileUpload1.FileContent);
                //get the file extention to use for saving the output file
                string fileName = FileUpload1.FileName.ToString();
                int idx = fileName.IndexOf(".");
                _inputFileExtention = fileName.Substring(idx+1);//e.g. "csv"
            }
            else
            {
                divResult.InnerText = "Missing input file!";
            }
            // set a time out for polling status
            const int TimeOutInMilliseconds = 120 * 1000; // Set a timeout of 2 minutes

            using (HttpClient client = new HttpClient())
            {
                var request = new BatchScoreRequest()
                {

                    Input = new AzureBlobDataReference()
                    {
                        ConnectionString = storageConnectionString,
                        RelativeLocation = blob.Uri.LocalPath
                    },
                    GlobalParameters = new Dictionary<string, string>()
                    {
                    }
                };

                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);

                divResult.InnerHtml += "Submitting the job...<br/>";

                // submit the job
                var response = await client.PostAsJsonAsync(BaseUrl, request).ConfigureAwait(false);
                if (!response.IsSuccessStatusCode)
                {
                    await WriteFailedResponse(response).ConfigureAwait(false);
                    return;
                }

                string jobId = await response.Content.ReadAsAsync<string>().ConfigureAwait(false);
                divResult.InnerHtml += string.Format("Job ID: {0}</br>", jobId);

                string jobLocation = BaseUrl + "/" + jobId + "";
                Stopwatch watch = Stopwatch.StartNew();
                bool done = false;
                bool isRunning = false;
                while (!done)
                {
                    response = await client.GetAsync(jobLocation).ConfigureAwait(false);
                    if (!response.IsSuccessStatusCode)
                    {
                        await WriteFailedResponse(response).ConfigureAwait(false);
                        return;
                    }

                    BatchScoreStatus status = await response.Content.ReadAsAsync<BatchScoreStatus>().ConfigureAwait(false);
                    if (watch.ElapsedMilliseconds > TimeOutInMilliseconds)
                    {
                        done = true;
                        divResult.InnerHtml += string.Format("Timed out. Deleting job {0} ...</br>", jobId);
                        await client.DeleteAsync(jobLocation).ConfigureAwait(false);
                    }
                    switch (status.StatusCode)
                    {
                        case BatchScoreStatusCode.NotStarted:
                            //divResult.InnerHtml += string.Format("Job {0} not yet started...</br>", jobId);
                            break;
                        case BatchScoreStatusCode.Running:
                            if (!isRunning)
                                divResult.InnerHtml += string.Format("Job {0} running...</br>", jobId);
                            isRunning = true;
                            break;
                        case BatchScoreStatusCode.Failed:
                            divResult.InnerHtml += string.Format("Job {0} failed!</br>", jobId);
                            divResult.InnerHtml += string.Format("Error details: {0}</br>", status.Details);
                            done = true;
                            break;
                        case BatchScoreStatusCode.Cancelled:
                            divResult.InnerHtml += string.Format("Job {0} cancelled!</br>", jobId);
                            done = true;
                            break;
                        case BatchScoreStatusCode.Finished:
                            done = true;
                            divResult.InnerHtml += string.Format("Job {0} finished!</br>", jobId);

                            ProcessResults(status);
                            break;
                    }

                    if (!done)
                    {
                        Thread.Sleep(1000); // Wait one second
                    }
                }
            }
        }
    }

    public class AzureBlobDataReference
    {
        // Storage connection string used for regular blobs. It has the following format:
        // DefaultEndpointsProtocol=https;AccountName=ACCOUNT_NAME;AccountKey=ACCOUNT_KEY
        // It's not used for shared access signature blobs.
        public string ConnectionString { get; set; }

        // Relative uri for the blob, used for regular blobs as well as shared access 
        // signature blobs.
        public string RelativeLocation { get; set; }

        // Base url, only used for shared access signature blobs.
        public string BaseLocation { get; set; }

        // Shared access signature, only used for shared access signature blobs.
        public string SasBlobToken { get; set; }
    }

    public enum BatchScoreStatusCode
    {
        NotStarted,
        Running,
        Failed,
        Cancelled,
        Finished
    }

    public class BatchScoreStatus
    {
        // Status code for the batch scoring job
        public BatchScoreStatusCode StatusCode { get; set; }


        // Location for the batch scoring output
        public AzureBlobDataReference Result { get; set; }

        // Error details, if any
        public string Details { get; set; }
    }

    public class BatchScoreRequest
    {
        public AzureBlobDataReference Input { get; set; }
        public IDictionary<string, string> GlobalParameters { get; set; }
    }
}